﻿using System;
using System.Collections.Generic;
using System.Linq;
using CivilRegistry.ControlledModel.Extensions;

namespace CivilRegistry.ControlledModel
{
    /// <summary>
    /// The person object.
    /// </summary>
    public partial class Person: User
    {
        #region Methods

        #region Finder Methods

        /// <summary>
        /// Finds the <see cref="Person"/> by Id.
        /// </summary>
        /// <param name="id">The id of the <see cref="Person"/>.</param>
        /// <returns>Found <see cref="Person"/> object, null if not found.</returns>
        public static Person FindById(int id)
        {
            return (Person)repository.FindBy(user => user.UserId == id && user is Person);
        }

        /// <summary>
        /// Retrives the deactive <see cref="Person"/>s.
        /// </summary>
        /// <returns>The deactive <see cref="Person"/>s.</returns>
        public static List<Person> GetDeactiveRecords()
        {
            List<Person> personList = new List<Person>();
            repository.FindAll(person => person.DeactivateReason != null).ForEach(person => personList.Add((Person)person));
            return personList;
        }

        /// <summary>
        /// Retrives the non-active <see cref="Person"/> objects whos age is grater than or equal to 15.
        /// </summary>
        /// <returns>Found <see cref="Person"/> objects, empty if none are found.</returns>
        public static List<Person> GetNonActivatedUsers()
        {
            List<Person> personList = new List<Person>();

            repository.FindAll(user => user is Person && user.DeactivateReason == "Not-yet-Activated" && user.Age >= 15).ForEach(person => personList.Add((Person)person));

            return personList;
        }

        /// <summary>
        /// Retrives the <see cref="Person"/> objects who's age is grater than 18 and not having <see cref="VoterCard"/>.
        /// </summary>
        /// <returns>Found <see cref="Person"/> objects, empty if none are found.</returns>
        public static List<Person> GetAbove18UsersWithoutHavingVoterCard()
        {
            List<Person> personList = new List<Person>();

            repository.FindAll(user => user is Person && user.VoterCards.FirstOrDefault() == null && Unsubscription.FindBy(user, UnsubscriptionType.FindById(1)) == null && user.Age >= 18)
                .ForEach(person => personList.Add((Person)person));

            return personList;
        }

        #endregion

        #region Card Request Methods

        /// <summary>
        /// To apply for <see cref="BirthCertificate"/>.
        /// </summary>
        /// <param name="birthCertificae">The filled <see cref="BirthCertificate"/>.</param>
        /// <param name="relationWithChild">Relation of the applier with child.</param>
        /// <param name="verificationCode">The verification code.</param>
        /// <returns>The <see cref="BirthCertificate"/>.</returns>
        public BirthCertificate ApplyForBirthCertificate(BirthCertificate birthCertificae, Relation relationWithChild, string verificationCode)
        {
            birthCertificae.Required("birthCertificae");

            BirthReport birthReport = BirthReport.FindByVerificationCode(verificationCode, this.UserId);

            if (birthReport != null)
            {
                if (birthReport.IsApplied != null && birthReport.IsApplied.Value)
                {
                    throw new ApplicationException("You have already applied for this BirthCertificate.");
                }
                else
                {
                    //Create a user/person account for child.
                    birthCertificae.Person = this.CreatePersonAccount(new Person(), birthCertificae, relationWithChild, birthReport);
                    birthCertificae.IsApproved = false;
                    birthReport.IsApplied = true;
                    birthCertificae.BirthReport = birthReport;
                    birthCertificae.AddedUser = this;
                    birthCertificae.AddedDate = DateTime.Now;

                    return BirthCertificate.Insert(birthCertificae);
                }
            }
            else
            {
                throw new ApplicationException("Verification Failed!");
            }
        }

        /// <summary>
        /// To apply for <see cref="VoterCard"/>.
        /// </summary>
        /// <param name="voterCard">The <see cref="VoterCard"/> to apply.</param>
        /// <returns>The <see cref="VoterCard"/>.</returns>
        public VoterCard ApplyForVoterCard(VoterCard voterCard)
        {
            voterCard.Required("voterCard");
            voterCard.IsApproved = false;
            voterCard.AddedUser = this;
            voterCard.AddedDate = DateTime.Now;
            return VoterCard.Insert(voterCard);
        }

        /// <summary>
        /// To apply for <see cref="RationCard"/>.
        /// </summary>
        /// <param name="rationCard">The <see cref="RationCard"/> to apply.</param>
        /// <returns>The <see cref="RationCard"/>.</returns>
        public RationCard ApplyForRationCard(RationCard rationCard)
        {
            rationCard.AddedUser = this;
            rationCard.IsApproved = false;
            rationCard.AddedDate = DateTime.Now;
            return RationCard.Insert(rationCard);
        }

        /// <summary>
        /// To add member to the <see cref="RationCard"/>.
        /// </summary>
        /// <param name="rationCard">The <see cref="RationCard"/>.</param>
        /// <param name="member">The <see cref="Person"/> to add.</param>
        /// <returns>The <see cref="RationCardAlterRequest"/>.</returns>
        public RationCardAlterRequest AddMemberToRationCardRequest(RationCard rationCard, Person member)
        {
            RationCardAlterRequest request = new RationCardAlterRequest();
            request.ApplicableUser = member;
            request.RequestedUser = this;
            request.RationCardAlterRequestType = RationCardAlterRequestType.FindById((int)RationCardAlterRequestTypeEnum.AddMember);
            request = RationCardAlterRequest.Insert(request);

            rationCard.IsApproved = false;
            rationCard.ModifiedDate = DateTime.Now;
            rationCard.ModifiedUser = this;
            rationCard.SaveChanges();

            return request;
        }

        /// <summary>
        /// To remove member from the <see cref="RationCard"/>.
        /// </summary>
        /// <param name="rationCard">The <see cref="RationCard"/>.</param>
        /// <param name="member">The <see cref="Person"/> to remove.</param>
        /// <returns>The <see cref="RationCardAlterRequest"/>.</returns>
        public RationCardAlterRequest RemoveMemberFromRationCardRequest(RationCard rationCard, Person member)
        {
            RationCardAlterRequest request = new RationCardAlterRequest();
            request.ApplicableUser = member;
            request.RequestedUser = this;
            request.RationCardAlterRequestType = RationCardAlterRequestType.FindById((int)RationCardAlterRequestTypeEnum.RemoveMember);
            request = RationCardAlterRequest.Insert(request);

            rationCard.IsApproved = false;
            rationCard.ModifiedDate = DateTime.Now;
            rationCard.ModifiedUser = this;
            rationCard.SaveChanges();

            return request;
        }

        #endregion

        /// <summary>
        /// To create <see cref="Person"/> account.
        /// </summary>
        /// <param name="child">The <see cref="Person"/> to create.</param>
        /// <param name="birthCertificae">The <see cref="BirthCertificate"/> to refer for the creation of child(<see cref="Person"/>) account.</param>
        /// <param name="relationWithChild">The relation with child.</param>
        /// <param name="birthReport">The <see cref="BirthReport"/>.</param>
        /// <returns>Created <see cref="Person"/>.</returns>
        private Person CreatePersonAccount(Person child, BirthCertificate birthCertificae, Relation relationWithChild, BirthReport birthReport)
        {
            if (User.IsUserNameExists(birthCertificae.ChildFirshtName))
            {
                child.UserName = birthCertificae.ChildFirshtName + DateTime.Now.Second;
            }
            else
            {
                child.UserName = birthCertificae.ChildFirshtName;
            }

            child.Password = this.GetRandomPassword(5);

            child.FirstName = birthCertificae.ChildFirshtName;
            child.MiddleName = birthCertificae.ChildMiddleName;
            child.LastName = birthCertificae.ChildLastName;

            child.DateOfBirth = birthReport.DateOfBirthOftheChild;

            switch (relationWithChild)
            {
                case Relation.Father:
                    child.FatherName = this.FirstName;
                    break;
                case Relation.Mother:
                    child.MotherName = this.FirstName;
                    break;
                default:
                    child.GaurdianName = this.FirstName;
                    break;
            }

            child.PermanentAddress = this.PermanentAddress;
            child.PresentAddress = this.PresentAddress;
            child.EmailAddress = this.EmailAddress;
            child.PhoneNo = this.PhoneNo;

            child.Gender = birthReport.GenderOftheChild;

            child.Family = this.Family;
            child.DeactivateReason = "Not-yet-Activated";

            child.AddedUser = this;
            child.AddedDate = DateTime.Now;
            
            Person.Validate(child);

            return child;
        }

        /// <summary>
        /// Generates a random password.
        /// </summary>
        /// <param name="length">The length of the password.</param>
        /// <returns>Generated password.</returns>
        private string GetRandomPassword(int length)
        {
            char[] chars = "$%#@!*abcdefghijklmnopqrstuvwxyz1234567890?;:ABCDEFGHIJKLMNOPQRSTUVWXYZ^&".ToCharArray();
            string password = string.Empty;
            Random random = new Random();

            for (int i = 0; i < length; i++)
            {
                int x = random.Next(1, chars.Length);
                //Don't Allow Repetation of Characters
                if (!password.Contains(chars.GetValue(x).ToString()))
                    password += chars.GetValue(x);
                else
                    i--;
            }
            return password;
        }

        #endregion
    }
}